package references

import (
	"fmt"
	"jvm/instructions/base"
	"jvm/rtda"
	"jvm/rtda/heap"
)

// Invoke instance method; dispatch based on class
type INVOKE_VIRTUAL struct{ base.Index16Instruction }

// hack!
func (iv *INVOKE_VIRTUAL) Execute(jFrame *rtda.JFrame) {
	currentClass := jFrame.Method().Class()
	cp := jFrame.Method().Class().ConstantPool()
	methodRef := cp.GetConstant(iv.Index).(*heap.MethodRef)
	resolvedMethod := methodRef.ResolvedMethod()

	if resolvedMethod.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}

	ref := jFrame.OperandStack().GetRefFromTop(resolvedMethod.ArgSlotCount() - 1)
	if ref == nil {
		if methodRef.Name() == "println" {
			stack := jFrame.OperandStack()
			switch methodRef.Descriptor() {
			case "(Z)V":
				fmt.Printf("%v\n", stack.PopInt() != 0)
			case "(C)V":
				fmt.Printf("%c\n", stack.PopInt())
			case "(I)V", "(B)V", "(S)V":
				fmt.Printf("%v\n", stack.PopInt())
			case "(F)V":
				fmt.Printf("%v\n", stack.PopFloat())
			case "(J)V":
				fmt.Printf("%v\n", stack.PopLong())
			case "(D)V":
				fmt.Printf("%v\n", stack.PopDouble())
			case "(Ljava/lang/String;)V":
				jStr := stack.PopRef()
				goStr := heap.GoString(jStr)
				fmt.Println(goStr)
			case "(Ljava/lang/Object;)V":
				println("obj")
				return
			default:
				panic("println: " + methodRef.Descriptor())
			}
			stack.PopRef()
			return
		}
		panic("java.lang.NullPointerException")
	}

	if resolvedMethod.IsProtected() && resolvedMethod.Class().IsSuperClassOf(currentClass) &&
		resolvedMethod.Class().GetPackageName() != currentClass.GetPackageName() &&
		ref.Class() != currentClass && !ref.Class().IsSubClassOf(currentClass) {
		panic("java.lang.IllegalAccessError")
	}

	methodToBeInvoked := heap.LookupMethodInClass(ref.Class(), methodRef.Name(), methodRef.Descriptor())
	if methodToBeInvoked == nil || methodToBeInvoked.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}
	base.InvokeMethod(jFrame, methodToBeInvoked)
}
